home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / lib / totem / plugins / bbc / contentview.pyc (.txt) < prev    next >
Encoding:
Python Compiled Bytecode  |  2009-04-20  |  32.5 KB  |  982 lines

  1. # Source Generated with Decompyle++
  2. # File: in.pyc (Python 2.6)
  3.  
  4. import gobject
  5. gobject.threads_init()
  6. import glib
  7. import gio
  8. import pygst
  9. pygst.require('0.10')
  10. import gst
  11. import gtk
  12. import pango
  13. import os
  14. import dircache
  15. import errno
  16. import random
  17. import time
  18. import thread
  19. from rdflib.Graph import ConjunctiveGraph
  20. from rdflib import Namespace
  21. from rdflib import RDF
  22. from xdg import BaseDirectory
  23. import installablecodecs
  24. import genres
  25. DC = Namespace('http://purl.org/dc/elements/1.1/')
  26. PO = Namespace('http://purl.org/ontology/po/')
  27. OWL = Namespace('http://www.w3.org/2002/07/owl#')
  28. FOAF = Namespace('http://xmlns.com/foaf/0.1/')
  29. PLAY = Namespace('http://uriplay.org/elements/')
  30. codec_cache = None
  31. container_map = {
  32.     'application/ogg': 'application/ogg',
  33.     'audio/ogg': 'application/ogg',
  34.     'video/ogg': 'application/ogg',
  35.     'video/x-ms-asf': 'video/x-ms-asf',
  36.     'audio/x-ms-asf': 'video/x-ms-asf',
  37.     'audio/mp3': 'audio/mpeg, mpegversion=(int)1, layer=(int)3',
  38.     'audio/mp4': 'audio/x-m4a',
  39.     'audio/mpeg': 'audio/mpeg, mpegversion=(int)1',
  40.     'video/x-flv': 'video/x-flv',
  41.     'video/3gpp': 'application/x-3gp',
  42.     'application/x-3gp': 'application/x-3gp',
  43.     'audio/x-matroska': 'video/x-matroska',
  44.     'video/x-matroska': 'video/x-matroska',
  45.     'video/mp4': 'video/quicktime',
  46.     'video/mpeg': 'video/mpeg, mpegversion=(int)1, ' + ' systemstream=(boolean)true;   ' + 'video/mpeg, mpegversion=(int)2, ' + ' systemstream=(boolean)true',
  47.     'video/mpeg2': 'video/mpeg, mpegversion=(int)2,' + ' systemstream=(boolean)false',
  48.     'video/mp2t': 'video/mpegts',
  49.     'video/mpegts': 'video/mpegts' }
  50. audio_map = {
  51.     'audio/mp1': 'audio/mpeg, mpegversion=(int)1, layer=(int)1',
  52.     'audio/mp2': 'audio/mpeg, mpegversion=(int)1, layer=(int)2',
  53.     'audio/mp3': 'audio/mpeg, mpegversion=(int)1, layer=(int)3',
  54.     'audio/mp4': 'audio/mpeg, mpegversion=(int)2; ' + 'audio/mpeg, mpegversion=(int)4',
  55.     'audio/mpeg': 'audio/mpeg, mpegversion=(int)1, layer=(int)3',
  56.     'audio/x-wma': 'audio/x-wma, wmaversion=(int)1; ' + 'audio/x-wma, wmaversion=(int)2',
  57.     'audio/x-wmv': 'audio/x-wma, wmaversion=(int)1; ' + 'audio/x-wma, wmaversion=(int)2',
  58.     'audio/x-ms-wma': 'audio/x-wma, wmaversion=(int)1; ' + 'audio/x-wma, wmaversion=(int)2',
  59.     'audio/x-ms-wmv': 'audio/x-wma, wmaversion=(int)1; ' + 'audio/x-wma, wmaversion=(int)2',
  60.     'audio/vorbis': 'audio/x-vorbis' }
  61. video_map = {
  62.     'video/x-vp6': 'video/x-vp6',
  63.     'video/x-flash-video': 'video/x-svq, svqversion=(int)1; ' + 'video/x-svq, svqversion=(int)3; ' + 'video/x-flash-video, flvversion=(int)1',
  64.     'video/H263-200': 'video/x-svq, svqversion=(int)1; ' + 'video/x-flash-video, flvversion=(int)1',
  65.     'video/x-svq': 'video/x-svq, svqversion=(int)1; ' + 'video/x-svq, svqversion=(int)3; ' + 'video/x-flash-video, flvversion=(int)1',
  66.     'video/H264': 'video/x-h264',
  67.     'video/mpeg': 'video/mpeg, mpegversion=(int)1, ' + ' systemstream=(boolean)false;   ' + 'video/mpeg, mpegversion=(int)2, ' + ' systemstream=(boolean)false',
  68.     'video/mpeg1': 'video/mpeg, mpegversion=(int)1, ' + ' systemstream=(boolean)false',
  69.     'video/mpeg2': 'video/mpeg, mpegversion=(int)2, ' + ' systemstream=(boolean)false',
  70.     'video/x-dirac': 'video/x-dirac',
  71.     'video/x-wmv': 'video/x-wmv, wmvversion=(int)1; ' + 'video/x-wmv, wmvversion=(int)2; ' + 'video/x-wmv, wmvversion=(int)3',
  72.     'video/x-ms-wmv': 'video/x-wmv, wmvversion=(int)1; ' + 'video/x-wmv, wmvversion=(int)2; ' + 'video/x-wmv, wmvversion=(int)3' }
  73.  
  74. class CodecCache(gobject.GObject):
  75.     __slots__ = [
  76.         'codec_cache',
  77.         'installable_codecs']
  78.     __gsignals__ = dict(loaded = (gobject.SIGNAL_RUN_LAST, None, ()))
  79.     
  80.     def __init__(self):
  81.         gobject.GObject.__init__(self)
  82.         self.codec_cache = { }
  83.         self.installable_codecs = None
  84.  
  85.     
  86.     def reload_async(self):
  87.         gst.log('starting codec cache loading')
  88.         thread.start_new_thread(self._loading_thread, ())
  89.  
  90.     
  91.     def _loading_thread(self):
  92.         ''' idle callback to marshal result back into the main thread '''
  93.         
  94.         def _loading_done_idle_cb(res):
  95.             gst.log('codec cache loaded (%d elements)' % len(res))
  96.             self.installable_codecs = res
  97.             self.emit('loaded')
  98.             return False
  99.  
  100.         gst.log('in codec cache loading thread')
  101.         res = installablecodecs.getInstallableCodecs()
  102.         gst.log('codec cache loading done, marshalling result into main thread')
  103.         gobject.idle_add(_loading_done_idle_cb, res)
  104.  
  105.     
  106.     def haveDecoderForCaps(self, decoder_caps):
  107.         caps_string = decoder_caps.to_string()
  108.         if caps_string in self.codec_cache:
  109.             return self.codec_cache[caps_string]
  110.         registry = gst.registry_get_default()
  111.         features = registry.get_feature_list(gst.TYPE_ELEMENT_FACTORY)
  112.         for feature in features:
  113.             if feature.get_rank() < gst.RANK_MARGINAL:
  114.                 continue
  115.             
  116.             klass = feature.get_klass()
  117.             if klass.find('Demux') >= 0 and klass.find('Decoder') >= 0 or klass.find('Parse') >= 0:
  118.                 for pad_template in feature.get_static_pad_templates():
  119.                     if pad_template.direction == gst.PAD_SINK:
  120.                         if not pad_template.get_caps().intersect(decoder_caps).is_empty():
  121.                             self.codec_cache[caps_string] = True
  122.                             gst.debug('%s can handle %s' % (feature.get_name(), caps_string))
  123.                             return True
  124.                         continue
  125.                     pad_template.get_caps().intersect(decoder_caps).is_empty()
  126.                 
  127.         
  128.         self.codec_cache[caps_string] = False
  129.         gst.debug('no element found that can handle ' + caps_string)
  130.         return False
  131.  
  132.     
  133.     def isInstalledOrInstallable(self, caps_needed):
  134.         if not caps_needed and caps_needed.is_empty() or caps_needed.is_any():
  135.             return False
  136.         if self.installable_codecs is None:
  137.             gst.log('database of installable codecs not loaded yet')
  138.             return False
  139.         for s in caps_needed:
  140.             if not self.haveDecoderForCaps(gst.Caps(s)):
  141.                 gst.debug('no decoder for %s installed' % s.to_string())
  142.                 if s.get_name() not in self.installable_codecs:
  143.                     gst.debug('%s not installable either' % s.to_string())
  144.                     return False
  145.                 continue
  146.             s.get_name() not in self.installable_codecs
  147.         
  148.         return True
  149.  
  150.  
  151.  
  152. class UriPlayObject(object):
  153.     __slots__ = [
  154.         'rdf_attribute_mapping']
  155.     
  156.     def __init__(self):
  157.         self.rdf_attribute_mapping = []
  158.  
  159.     
  160.     def parseProperties(self, conjunctive_graph, graph_obj):
  161.         for rdf_tag, prop_name in self.rdf_attribute_mapping:
  162.             self.__setattr__(prop_name, None)
  163.         
  164.         for rdf_tag, prop_name in self.rdf_attribute_mapping:
  165.             for match in conjunctive_graph.objects(graph_obj, rdf_tag):
  166.                 self.__setattr__(prop_name, match.encode('utf-8'))
  167.             
  168.         
  169.  
  170.  
  171.  
  172. class Brand(UriPlayObject):
  173.     __slots__ = [
  174.         'title',
  175.         'description',
  176.         'episodes',
  177.         'genres']
  178.     
  179.     def __init__(self):
  180.         self.episodes = []
  181.         self.genres = []
  182.         self.rdf_attribute_mapping = [
  183.             (DC['title'], 'title'),
  184.             (DC['description'], 'description')]
  185.  
  186.     
  187.     def parseBrand(self, conjunctive_graph, graph_brand):
  188.         self.parseProperties(conjunctive_graph, graph_brand)
  189.         self.episodes = []
  190.         for e in conjunctive_graph.objects(graph_brand, PO['episode']):
  191.             episode = Episode()
  192.             episode.parseEpisode(conjunctive_graph, e)
  193.             self.episodes.append(episode)
  194.         
  195.         self.genres = []
  196.         for match in conjunctive_graph.objects(graph_brand, PO['genre']):
  197.             genre_utf8 = match.encode('utf-8')
  198.             pos = genre_utf8.find('/genres/')
  199.             if pos > 0:
  200.                 pos += len('/genres/')
  201.                 genre = genre_utf8[pos:]
  202.             else:
  203.                 gst.warning('Unexpected genre identifier: ' + genre_utf8)
  204.                 genre = 'other'
  205.             if genre not in self.genres:
  206.                 self.genres.append(genre)
  207.                 continue
  208.         
  209.  
  210.     
  211.     def hasUsableEpisodes(self):
  212.         for episode in self.episodes:
  213.             if episode.hasUsableEncodings():
  214.                 return True
  215.         
  216.         return False
  217.  
  218.     
  219.     def getUsableEpisodes(self):
  220.         usable_episodes = []
  221.         for episode in self.episodes:
  222.             if episode.hasUsableEncodings():
  223.                 usable_episodes.append(episode)
  224.                 continue
  225.         
  226.         return usable_episodes
  227.  
  228.  
  229.  
  230. class Episode(UriPlayObject):
  231.     __slots__ = [
  232.         'title',
  233.         'description',
  234.         'versions',
  235.         'encodings']
  236.     
  237.     def __init__(self):
  238.         self.encodings = []
  239.         self.rdf_attribute_mapping = [
  240.             (DC['title'], 'title'),
  241.             (DC['description'], 'description')]
  242.  
  243.     
  244.     def parseEpisode(self, conjunctive_graph, graph_episode):
  245.         self.parseProperties(conjunctive_graph, graph_episode)
  246.         self.versions = []
  247.         for v in conjunctive_graph.objects(graph_episode, PO['version']):
  248.             version = EpisodeVersion()
  249.             version.parseVersion(conjunctive_graph, v)
  250.             self.versions.append(version)
  251.             if not self.encodings:
  252.                 self.encodings = version.encodings
  253.                 continue
  254.         
  255.  
  256.     
  257.     def hasUsableEncodings(self):
  258.         for encoding in self.encodings:
  259.             if encoding.isUsable():
  260.                 return True
  261.         
  262.         return False
  263.  
  264.     
  265.     def getBestEncoding(self, connection_speed = 0):
  266.         gst.log('connection speed: %d kbit/s' % connection_speed)
  267.         best_encoding = None
  268.         for encoding in self.encodings:
  269.             if not encoding.isUsable():
  270.                 continue
  271.             
  272.             gst.log('have encoding with bitrate: %d kbit/s' % encoding.getBitrate())
  273.             if best_encoding:
  274.                 if encoding.getBitrate() > best_encoding.getBitrate():
  275.                     if connection_speed <= 0 or encoding.getBitrate() <= connection_speed:
  276.                         best_encoding = encoding
  277.                     
  278.                 
  279.             encoding.getBitrate() > best_encoding.getBitrate()
  280.             best_encoding = encoding
  281.         
  282.         if best_encoding:
  283.             gst.log('best encoding has bitrate of %d kbit/s' % best_encoding.getBitrate())
  284.         
  285.         return best_encoding
  286.  
  287.     
  288.     def getUri(self, connection_speed = 0):
  289.         encoding = self.getBestEncoding(connection_speed)
  290.         if encoding:
  291.             location = encoding.getBestLocation()
  292.             if location:
  293.                 return location.uri
  294.         
  295.  
  296.  
  297.  
  298. class EpisodeVersion(UriPlayObject):
  299.     __slots__ = [
  300.         'encodings']
  301.     
  302.     def __init__(self):
  303.         self.encodings = []
  304.         self.rdf_attribute_mapping = []
  305.  
  306.     
  307.     def parseVersion(self, conjunctive_graph, graph_version):
  308.         self.parseProperties(conjunctive_graph, graph_version)
  309.         self.encodings = []
  310.         for e in conjunctive_graph.objects(graph_version, PLAY['manifestedAs']):
  311.             encoding = Encoding()
  312.             encoding.parseEncoding(conjunctive_graph, e)
  313.             self.encodings.append(encoding)
  314.         
  315.  
  316.  
  317.  
  318. class Encoding(UriPlayObject):
  319.     __slots__ = [
  320.         'container_format',
  321.         'bitrate',
  322.         'size',
  323.         'video_codec',
  324.         'video_bitrate',
  325.         'video_fps',
  326.         'video_height',
  327.         'video_width',
  328.         'audio_codec',
  329.         'audio_bitrate',
  330.         'audio_channels',
  331.         'locations',
  332.         'required_caps']
  333.     
  334.     def __init__(self):
  335.         self.required_caps = None
  336.         self.rdf_attribute_mapping = [
  337.             (PLAY['dataContainerFormat'], 'container_format'),
  338.             (PLAY['bitRate'], 'bitrate'),
  339.             (PLAY['dataSize'], 'size'),
  340.             (PLAY['videoCoding'], 'video_codec'),
  341.             (PLAY['videoBitrate'], 'video_bitrate'),
  342.             (PLAY['videoFrameRate'], 'video_fps'),
  343.             (PLAY['videoVerticalSize'], 'video_height'),
  344.             (PLAY['videoHorizontalSize'], 'video_width'),
  345.             (PLAY['audioCoding'], 'audio_codec'),
  346.             (PLAY['audioBitrate'], 'audio_bitrate'),
  347.             (PLAY['audioChannels'], 'audio_channels')]
  348.  
  349.     
  350.     def parseEncoding(self, conjunctive_graph, graph_encoding):
  351.         self.parseProperties(conjunctive_graph, graph_encoding)
  352.         self.locations = []
  353.         for l in conjunctive_graph.objects(graph_encoding, PLAY['availableAt']):
  354.             location = Location()
  355.             location.parseLocation(conjunctive_graph, l)
  356.             self.locations.append(location)
  357.             self.required_caps = self.postProcessCodecs()
  358.         
  359.  
  360.     
  361.     def postProcessCodecs(self):
  362.         required_caps = gst.Caps()
  363.         if self.video_codec:
  364.             self.video_codec = self.video_codec.lower()
  365.             if self.video_codec in video_map:
  366.                 required_caps.append(gst.Caps(video_map[self.video_codec]))
  367.             else:
  368.                 gst.warning('unmapped video codec ' + self.video_codec)
  369.                 return None
  370.         self.video_codec in video_map
  371.         if self.audio_codec:
  372.             self.audio_codec = self.audio_codec.lower()
  373.             if self.audio_codec in audio_map:
  374.                 required_caps.append(gst.Caps(audio_map[self.audio_codec]))
  375.             else:
  376.                 gst.warning('unmapped audio codec ' + self.audio_codec)
  377.                 return None
  378.         self.audio_codec in audio_map
  379.         if self.container_format:
  380.             self.container_format = self.container_format.lower()
  381.             if self.container_format in container_map:
  382.                 required_caps.append(gst.Caps(container_map[self.container_format]))
  383.             else:
  384.                 gst.warning('unmapped container format ' + self.container_format)
  385.                 return None
  386.         self.container_format in container_map
  387.         if not required_caps.is_empty():
  388.             return required_caps
  389.         return None
  390.  
  391.     
  392.     def isUsable(self):
  393.         if self.required_caps:
  394.             return codec_cache.isInstalledOrInstallable(self.required_caps)
  395.         return False
  396.  
  397.     
  398.     def getBitrate(self):
  399.         if not self.bitrate:
  400.             return 0
  401.         return eval(self.bitrate)
  402.  
  403.     
  404.     def getBestLocation(self):
  405.         locations = self.locations
  406.         random.shuffle(locations)
  407.         for loc in locations:
  408.             if loc.isUsable():
  409.                 return loc
  410.         
  411.  
  412.  
  413.  
  414. class Location(UriPlayObject):
  415.     __slots__ = [
  416.         'uri',
  417.         'type',
  418.         'sub_type',
  419.         'is_live']
  420.     
  421.     def __init__(self):
  422.         self.rdf_attribute_mapping = [
  423.             (PLAY['uri'], 'uri'),
  424.             (PLAY['transportType'], 'type'),
  425.             (PLAY['transportSubType'], 'sub_type'),
  426.             (PLAY['transportIsLive'], 'is_live')]
  427.  
  428.     
  429.     def parseLocation(self, conjunctive_graph, graph_location):
  430.         self.parseProperties(conjunctive_graph, graph_location)
  431.  
  432.     
  433.     def isUsable(self):
  434.         if self.uri and self.uri.startswith('http'):
  435.             return True
  436.         return False
  437.  
  438.  
  439.  
  440. class ContentPool(gobject.GObject):
  441.     __slots__ = [
  442.         'cache_dir',
  443.         'brands']
  444.     __gsignals__ = dict(codec_cache_loaded = (gobject.SIGNAL_RUN_LAST, None, ()), progress_message = (gobject.SIGNAL_RUN_LAST, None, (str,)), loading_error = (gobject.SIGNAL_RUN_LAST, None, (str,)), loading_done = (gobject.SIGNAL_RUN_LAST, None, ()))
  445.     CACHE_FILE_PREFIX = 'content-'
  446.     CACHE_FILE_SUFFIX = '.cache'
  447.     AVAILABLE_CONTENT_URI = 'http://open.bbc.co.uk/rad/uriplay/availablecontent'
  448.     MAX_CACHE_FILE_AGE = 7200
  449.     
  450.     def __init__(self):
  451.         gobject.GObject.__init__(self)
  452.         self.brands = []
  453.         self.cache_dir = os.path.join(BaseDirectory.xdg_cache_home, 'totem', 'plugins', 'bbc')
  454.         
  455.         try:
  456.             os.makedirs(self.cache_dir)
  457.             gst.log('created cache directory ' + self.cache_dir)
  458.         except OSError:
  459.             err = None
  460.             if err.errno == errno.EEXIST:
  461.                 gst.log('cache directory ' + self.cache_dir + ' already exists')
  462.             else:
  463.                 gst.error('failed to create cache directory ' + self.cache_dir + ': ' + err.strerror)
  464.                 self.cache_dir = None
  465.         except:
  466.             err.errno == errno.EEXIST
  467.  
  468.  
  469.     
  470.     def _on_codec_cache_loaded(self, pool):
  471.         self.emit('codec-cache-loaded')
  472.  
  473.     
  474.     def isCacheFileName(self, filename):
  475.         if not filename.startswith(self.CACHE_FILE_PREFIX):
  476.             return False
  477.         if not filename.endswith(self.CACHE_FILE_SUFFIX):
  478.             return False
  479.         return True
  480.  
  481.     
  482.     def deleteStaleCacheFiles(self, except_etag = None):
  483.         
  484.         try:
  485.             for fn in dircache.listdir(self.cache_dir):
  486.                 if self.isCacheFileName(fn):
  487.                     if except_etag == None or fn.find(except_etag) < 0:
  488.                         
  489.                         try:
  490.                             gst.log('deleting stale cache file ' + fn)
  491.                             os.remove(os.path.join(self.cache_dir, fn))
  492.                         except OSError:
  493.                             pass
  494.                         except:
  495.                             None<EXCEPTION MATCH>OSError
  496.                         
  497.  
  498.                     None<EXCEPTION MATCH>OSError
  499.                     continue
  500.         except OSError:
  501.             pass
  502.  
  503.  
  504.     
  505.     def findMostRecentCacheFile(self):
  506.         best_mtime = 0
  507.         best_name = None
  508.         
  509.         try:
  510.             gst.log('Looking for cache files in ' + self.cache_dir)
  511.             for fn in dircache.listdir(self.cache_dir):
  512.                 if self.isCacheFileName(fn):
  513.                     mtime = os.stat(os.path.join(self.cache_dir, fn)).st_mtime
  514.                     gst.log('Found cache file %s, mtime %ld' % (fn, long(mtime)))
  515.                     if mtime > best_mtime:
  516.                         best_name = fn
  517.                         best_mtime = mtime
  518.                     
  519.                 mtime > best_mtime
  520.         except OSError:
  521.             err = None
  522.             gst.debug("couldn't inspect cache directory %s: %s" % (self.cache_dir, err.strerror))
  523.             return None
  524.  
  525.         if not best_name:
  526.             gst.log('No cache file found')
  527.             return None
  528.         return best_name
  529.  
  530.     
  531.     def getCacheETag(self):
  532.         etag = self.findMostRecentCacheFile()
  533.         if not etag:
  534.             return None
  535.         prefix_len = len(self.CACHE_FILE_PREFIX)
  536.         suffix_len = len(self.CACHE_FILE_SUFFIX)
  537.         etag = etag[prefix_len:-suffix_len]
  538.         gst.log('ETag: ' + etag)
  539.         return etag
  540.  
  541.     
  542.     def createCacheFileName(self, etag):
  543.         if not etag:
  544.             gst.debug('No ETag, using dummy ETag as fallback')
  545.             etag = '000000-00000-00000000'
  546.         
  547.         fn = self.CACHE_FILE_PREFIX + etag + self.CACHE_FILE_SUFFIX
  548.         return os.path.join(self.cache_dir, fn)
  549.  
  550.     
  551.     def parse_async(self, cache_fn):
  552.         self.emit('progress-message', _('Parsing available content list ...'))
  553.         thread.start_new_thread(self._parsing_thread, (cache_fn,))
  554.  
  555.     
  556.     def _parsing_thread(self, cache_fn):
  557.         
  558.         def _parse_idle_cb(err_msg, brands):
  559.             self.brands = brands
  560.             gst.info('Parsing done: %d brands' % len(self.brands))
  561.             if err_msg:
  562.                 self.emit('loading-error', err_msg)
  563.             else:
  564.                 self.emit('loading-done')
  565.             return False
  566.  
  567.         err_msg = None
  568.         brands = []
  569.         gst.debug('Loading ' + cache_fn)
  570.         store = ConjunctiveGraph()
  571.         
  572.         try:
  573.             gst.debug('Reading RDF file ...')
  574.             store.load(cache_fn)
  575.             gst.debug('Parsing ' + cache_fn)
  576.             brands = self.parseBrands(store)
  577.         except:
  578.             (None,)
  579.             gst.warning('Problem parsing RDF')
  580.             err_msg = 'Could not parse available content list'
  581.         finally:
  582.             gst.debug('Parsing done, marshalling result into main thread')
  583.             gobject.idle_add(_parse_idle_cb, err_msg, brands)
  584.  
  585.  
  586.     
  587.     def _format_size_for_display(self, size):
  588.         if size < 1024:
  589.             return '%d bytes' % size
  590.         if size < 1048576:
  591.             return '%.1f kB' % size / 1024
  592.         return '%.1f MB' % size / 1.04858e+06
  593.  
  594.     
  595.     def load_async(self):
  596.         global codec_cache
  597.         
  598.         def _query_done_cb(remote_file, result):
  599.             pdata = [
  600.                 [],
  601.                 0]
  602.             
  603.             def _read_async_cb(instream, result):
  604.                 
  605.                 try:
  606.                     partial_data = instream.read_finish(result)
  607.                     gst.log('Read partial chunk of %d bytes' % len(partial_data))
  608.                     chunks = pdata[0]
  609.                     bytes_read = pdata[1]
  610.                     if len(partial_data) == 0:
  611.                         instream.close()
  612.                         outstream = cache_file.create(gio.FILE_CREATE_NONE)
  613.                         for chunk in chunks:
  614.                             outstream.write(chunk)
  615.                         
  616.                         outsize = outstream.query_info('*').get_size()
  617.                         outstream.close()
  618.                         gst.info('Wrote %ld bytes' % outsize)
  619.                         self.parse_async(cache_fn)
  620.                     else:
  621.                         chunks.append(partial_data)
  622.                         bytes_read += len(partial_data)
  623.                         pdata[0] = chunks
  624.                         pdata[1] = bytes_read
  625.                         instream.read_async(10240, _read_async_cb, io_priority = glib.PRIORITY_LOW - 1)
  626.                         self.emit('progress-message', _('Downloading available content list ... ') + '(' + self._format_size_for_display(bytes_read) + ')')
  627.                 except IOError:
  628.                     e = None
  629.                     gst.warning('Error downloading ' + self.AVAILABLE_CONTENT_URI)
  630.                     instream.close()
  631.                     
  632.                     try:
  633.                         cache_file.delete()
  634.                     finally:
  635.                         self.emit('loading-error', _('Error downloading available content list'))
  636.  
  637.  
  638.  
  639.             gst.log('Query done')
  640.             
  641.             try:
  642.                 remote_info = remote_file.query_info_finish(result)
  643.             except Exception:
  644.                 (None, None, None, None, None)
  645.                 e = (None, None, None, None, None)
  646.                 gst.warning('Could not query %s: %s' % (self.AVAILABLE_CONTENT_URI, e.message))
  647.                 self.emit('loading-error', _('Could not connect to server'))
  648.                 return None
  649.  
  650.             gst.log('Got info, querying etag')
  651.             remote_etag = remote_info.get_etag()
  652.             if remote_etag:
  653.                 remote_etag = remote_etag.strip('"')
  654.                 gst.log('Remote etag: ' + remote_etag)
  655.             
  656.             cache_fn = self.createCacheFileName(remote_etag)
  657.             cache_file = gio.File(cache_fn)
  658.             
  659.             try:
  660.                 cache_size = cache_file.query_info('standard::size').get_size()
  661.             except:
  662.                 cache_size = 0
  663.             finally:
  664.                 pass
  665.  
  666.             
  667.             try:
  668.                 cache_file.delete()
  669.             except:
  670.                 pass
  671.  
  672.             remote_file.read().read_async(10240, _read_async_cb, io_priority = glib.PRIORITY_LOW - 1)
  673.             gst.info('copying ' + self.AVAILABLE_CONTENT_URI + ' -> ' + cache_fn)
  674.             self.emit('progress-message', _('Downloading available content list ...'))
  675.  
  676.         gst.log('starting loading')
  677.         if not codec_cache:
  678.             codec_cache = CodecCache()
  679.             codec_cache.connect('loaded', self._on_codec_cache_loaded)
  680.             codec_cache.reload_async()
  681.         
  682.         etag = self.getCacheETag()
  683.         if etag:
  684.             gst.log('Cached etag: ' + etag)
  685.             self.deleteStaleCacheFiles(etag)
  686.             existing_cache_fn = self.createCacheFileName(etag)
  687.             existing_cache_file = gio.File(existing_cache_fn)
  688.             existing_cache_info = existing_cache_file.query_info('time::modified')
  689.             existing_cache_mtime = existing_cache_info.get_modification_time()
  690.             secs_since_update = time.time() - existing_cache_mtime
  691.             if secs_since_update >= 0 and secs_since_update < self.MAX_CACHE_FILE_AGE:
  692.                 gst.log('Cache file is fairly recent, last updated %f secs ago' % secs_since_update)
  693.                 self.parse_async(existing_cache_fn)
  694.                 return None
  695.         else:
  696.             gst.log('Cached etag: None')
  697.         remote_file = gio.File(self.AVAILABLE_CONTENT_URI)
  698.         gst.log('Contacting server ' + self.AVAILABLE_CONTENT_URI)
  699.         self.emit('progress-message', _('Connecting to server ...'))
  700.         remote_file.query_info_async(_query_done_cb, '*')
  701.  
  702.     
  703.     def parseBrands(self, graph):
  704.         brands = []
  705.         for b in graph.subjects(RDF.type, PO['Brand']):
  706.             brand = Brand()
  707.             brand.parseBrand(graph, b)
  708.             brands.append(brand)
  709.             gst.log('[%3d eps] %s %s' % (len(brand.episodes), brand.title, brand.genres))
  710.         
  711.         return brands
  712.  
  713.     
  714.     def getUsableBrands(self):
  715.         usable_brands = []
  716.         for brand in self.brands:
  717.             if brand.hasUsableEpisodes():
  718.                 usable_brands.append(brand)
  719.                 continue
  720.         
  721.         return usable_brands
  722.  
  723.  
  724.  
  725. class ContentView(gtk.TreeView):
  726.     __slots__ = [
  727.         'pool',
  728.         'content_pool_loaded',
  729.         'codec_cache_loaded',
  730.         'genre_pool']
  731.     __gsignals__ = dict(play_episode = (gobject.SIGNAL_RUN_LAST, None, (object,)))
  732.     SORT_ID_1 = 0
  733.     
  734.     def __init__(self):
  735.         gtk.TreeView.__init__(self)
  736.         self.setupModel()
  737.         self.set_headers_visible(False)
  738.         self.connect('row-activated', self.onRowActivated)
  739.         self.set_property('has-tooltip', True)
  740.         self.connect('query-tooltip', self.onQueryTooltip)
  741.         self.set_message(_('Loading ...'))
  742.         self.pool = ContentPool()
  743.         self.pool.connect('codec-cache-loaded', self._on_codec_cache_loaded)
  744.         self.pool.connect('progress-message', self._on_content_pool_message)
  745.         self.pool.connect('loading-error', self._on_content_pool_error)
  746.         self.pool.connect('loading-done', self._on_content_pool_loading_done)
  747.         self.codec_cache_loaded = False
  748.         self.content_pool_loaded = False
  749.         self.genre_pool = genres.GenrePool()
  750.  
  751.     
  752.     def load(self):
  753.         self.pool.load_async()
  754.         gst.log('started loading')
  755.  
  756.     
  757.     def _on_content_pool_message(self, content_pool, msg):
  758.         self.set_message(msg)
  759.  
  760.     
  761.     def _on_content_pool_error(self, content_pool, err_msg):
  762.         gst.warning('Failed to load available content: ' + err_msg)
  763.         self.set_message(err_msg)
  764.  
  765.     
  766.     def _on_content_pool_loading_done(self, content_pool):
  767.         gst.log('content pool loaded')
  768.         self.content_pool_loaded = True
  769.         if self.codec_cache_loaded:
  770.             self.populate()
  771.         
  772.  
  773.     
  774.     def _on_codec_cache_loaded(self, content_pool):
  775.         gst.log('codec cache loaded, refilter')
  776.         self.codec_cache_loaded = True
  777.         if self.content_pool_loaded:
  778.             self.populate()
  779.         
  780.  
  781.     
  782.     def populate_add_genre(self, genre, parent_iter):
  783.         _iter = self.store.append(parent_iter, [
  784.             None,
  785.             None,
  786.             None,
  787.             genre])
  788.         for child_genre in genre.children:
  789.             self.populate_add_genre(child_genre, _iter)
  790.         
  791.         for brand in genre.brands:
  792.             brand_iter = self.store.append(_iter, [
  793.                 brand,
  794.                 None,
  795.                 None,
  796.                 None])
  797.             for ep in brand.episodes:
  798.                 self.store.append(brand_iter, [
  799.                     brand,
  800.                     ep,
  801.                     None,
  802.                     None])
  803.             
  804.         
  805.         return _iter
  806.  
  807.     
  808.     def populate(self):
  809.         gst.log('populating treeview')
  810.         brands = self.pool.getUsableBrands()
  811.         gst.info('%d brands with usable episodes/encodings' % len(brands))
  812.         self.genre_pool.clear()
  813.         for brand in brands:
  814.             for genre_shortref in brand.genres:
  815.                 genre = self.genre_pool.get_genre(genre_shortref)
  816.                 genre.add_brand(brand)
  817.             
  818.         
  819.         self.store.clear()
  820.         toplevel_iters = []
  821.         for toplevel_genre in self.genre_pool.get_toplevel_genres():
  822.             _iter = self.populate_add_genre(toplevel_genre, None)
  823.             toplevel_iters.append(_iter)
  824.         
  825.         self.set_model(self.filter)
  826.         for _iter in toplevel_iters:
  827.             path = self.store.get_path(_iter)
  828.             self.expand_row(path, False)
  829.         
  830.  
  831.     
  832.     def get_brand_tooltip(self, brand):
  833.         if not brand or not (brand.description):
  834.             return None
  835.         return '<b>%s</b>\n<i>%s</i>' % (gobject.markup_escape_text(brand.title), gobject.markup_escape_text(brand.description))
  836.  
  837.     
  838.     def get_episode_tooltip(self, brand, episode):
  839.         if not episode or not (episode.description):
  840.             return None
  841.         return '<b>%s</b>\n<b><small>%s</small></b>\n<i>%s</i>' % (gobject.markup_escape_text(brand.title), gobject.markup_escape_text(episode.title), gobject.markup_escape_text(episode.description))
  842.  
  843.     
  844.     def onQueryTooltip(self, view, x, y, keyboard_tip, tip):
  845.         
  846.         try:
  847.             (model, path, _iter) = self.get_tooltip_context(x, y, keyboard_tip)
  848.         except:
  849.             return False
  850.  
  851.         (brand, episode, msg, genre) = model.get(_iter, 0, 1, 2, 3)
  852.         if msg or genre:
  853.             return False
  854.         if brand and not episode:
  855.             markup = self.get_brand_tooltip(brand)
  856.         elif brand and episode:
  857.             markup = self.get_episode_tooltip(brand, episode)
  858.         else:
  859.             markup = None
  860.         if markup:
  861.             tip.set_markup(markup)
  862.         else:
  863.             tip.set_text(_('No details available'))
  864.         return True
  865.  
  866.     
  867.     def onRowActivated(self, view, path, col):
  868.         model = self.get_model()
  869.         if model:
  870.             _iter = model.get_iter(path)
  871.             (brand, episode) = self.get_model().get(_iter, 0, 1)
  872.             if episode:
  873.                 self.emit('play-episode', episode)
  874.             
  875.         
  876.  
  877.     
  878.     def renderGenreCell(self, column, renderer, model, _iter, genre):
  879.         markup = '<b>%s</b>' % gobject.markup_escape_text(genre.label)
  880.         renderer.set_property('markup', markup)
  881.  
  882.     
  883.     def renderBrandCell(self, column, renderer, model, _iter, brand):
  884.         markup = '<b><small>%s <span color="LightGray">(%d)</span></small></b>' % (gobject.markup_escape_text(brand.title), len(brand.episodes))
  885.         renderer.set_property('markup', markup)
  886.  
  887.     
  888.     def renderEpisodeCell(self, column, renderer, model, _iter, brand, episode):
  889.         markup = '<span><small>%s</small></span>' % gobject.markup_escape_text(episode.title)
  890.         renderer.set_property('markup', markup)
  891.  
  892.     
  893.     def renderMessageCell(self, column, renderer, model, _iter, msg):
  894.         markup = '<i>%s</i>' % gobject.markup_escape_text(msg)
  895.         renderer.set_property('markup', markup)
  896.  
  897.     
  898.     def renderCell(self, column, renderer, model, _iter):
  899.         (brand, episode, msg, genre) = model.get(_iter, 0, 1, 2, 3)
  900.         if msg:
  901.             self.renderMessageCell(column, renderer, model, _iter, msg)
  902.         elif genre:
  903.             self.renderGenreCell(column, renderer, model, _iter, genre)
  904.         elif not episode:
  905.             self.renderBrandCell(column, renderer, model, _iter, brand)
  906.         else:
  907.             self.renderEpisodeCell(column, renderer, model, _iter, brand, episode)
  908.  
  909.     
  910.     def sortFunc(self, model, iter1, iter2):
  911.         (brand1, episode1, genre1) = model.get(iter1, 0, 1, 3)
  912.         (brand2, episode2, genre2) = model.get(iter2, 0, 1, 3)
  913.         if genre1 and genre2:
  914.             if genre1.sort_rank != genre2.sort_rank:
  915.                 return genre1.sort_rank - genre2.sort_rank
  916.             s1 = genre1.label
  917.             s2 = genre2.label
  918.         elif genre1:
  919.             return -1
  920.         if genre2:
  921.             return 1
  922.         if not episode1 or not episode2:
  923.             s1 = brand1.title
  924.             s2 = brand2.title
  925.         elif episode1 and episode2:
  926.             s1 = episode1.title
  927.             s2 = episode2.title
  928.         else:
  929.             gst.warning('should not be reached (should be genre label comparison)')
  930.         if s1 == s2:
  931.             return 0
  932.         if s1 > s2:
  933.             return 1
  934.         return -1
  935.  
  936.     
  937.     def set_message(self, msg):
  938.         self.msg_store.clear()
  939.         self.msg_store.append(None, [
  940.             None,
  941.             None,
  942.             msg,
  943.             None])
  944.         self.set_model(self.msg_store)
  945.         gst.log('set message "' + msg + '"')
  946.  
  947.     
  948.     def setupModel(self):
  949.         self.msg_store = gtk.TreeStore(object, object, str, object)
  950.         self.store = gtk.TreeStore(object, object, str, object)
  951.         self.filter = self.store.filter_new()
  952.         column = gtk.TreeViewColumn()
  953.         renderer = gtk.CellRendererText()
  954.         renderer.set_property('ellipsize', pango.ELLIPSIZE_END)
  955.         column.pack_start(renderer, expand = True)
  956.         column.set_cell_data_func(renderer, self.renderCell)
  957.         self.append_column(column)
  958.         self.store.set_sort_func(self.SORT_ID_1, self.sortFunc)
  959.         self.store.set_sort_column_id(self.SORT_ID_1, gtk.SORT_ASCENDING)
  960.  
  961.  
  962. if __name__ == '__main__':
  963.     for cs in video_map:
  964.         caps = gst.Caps(video_map[cs])
  965.     
  966.     for cs in audio_map:
  967.         caps = gst.Caps(audio_map[cs])
  968.     
  969.     for cs in container_map:
  970.         caps = gst.Caps(container_map[cs])
  971.     
  972.     window = gtk.Window()
  973.     scrollwin = gtk.ScrolledWindow()
  974.     scrollwin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
  975.     window.add(scrollwin)
  976.     view = ContentView()
  977.     view.load()
  978.     scrollwin.add(view)
  979.     window.show_all()
  980.     gtk.main()
  981.  
  982.